package com.cus.tom.transfer.trans.io;

/*
 * DirectoryWriter 是对系统自带的 DocumentFile 的封装
 * 基于系统接口，实现文件夹的流化
 *
 * 目录树传输协议基于流式传输实现，无分包概念：
 * 4B文件名长度 | 8B文件长度 | 文件名 | 文件数据流 | (重复...)
 * 若文件名长度 = 文件长度 = 0，表示文件传输完毕
 * 若文件长度 = -1，表示是文件夹
 * 文件名采用UTF-8编码，包含文件的相对路径，并约定以"/"为路径分隔符
 */

import static com.cus.tom.transfer.trans.io.DirectoryReader.LOG_TAG;
import static com.plata.base.aclr.utils.NetworkUtils.close;

import android.content.ContentResolver;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;

import androidx.annotation.NonNull;
import androidx.documentfile.provider.DocumentFile;

import com.cus.tom.transfer.bean.FileType;
import com.cus.tom.transfer.bean.TypeFile;
import com.cus.tom.transfer.db.RecvDao;
import com.cus.tom.transfer.utils.Utils;

import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class DirectoryWriter extends Thread {
    private final ProgressReporter reporter;
    private final ContentResolver resolver;
    private final DocumentFile root;
    private final Channel in;
    private final BufferPool bufferPool;
    private ByteBuffer current;
    private boolean success = false;
    final String flag = UUID.randomUUID().toString();
    final RecvDao dao = new RecvDao();

    public DirectoryWriter(ContentResolver resolver, DocumentFile root, Channel in, ProgressReporter reporter,
                           BufferPool bufferPool) {
        this.resolver = resolver;
        this.root = root;
        this.in = in;
        this.reporter = reporter;
        this.bufferPool = bufferPool;
    }

    public String getFlag() {
        return flag;
    }

    public boolean isSuccess() {
        return success;
    }

    private DocumentFile makePath(@NonNull String[] segments) {
        DocumentFile parent = root;
        for (String name : segments) {
            DocumentFile current = null;
            for (DocumentFile file : parent.listFiles()) {
                if (name.equals(file.getName())) {
                    current = file;
                    break;
                }
            }
            if (current == null) {
                current = parent.createDirectory(name);
            }
            if (current == null) {
                return null;
            }
            parent = current;
        }
        return parent;
    }

    int recvNum = 0;

    private String writeFile(FileType type, final String path, final long length) throws IOException, InterruptedException {
        if (length == -1) { // is directory
            Log.d(LOG_TAG, "Now at: " + path);
//			makePath(path.split(Pattern.quote("/")));
            return null;
        }
        // is File
        Log.d(LOG_TAG, "writeFile: " + path + " length=" + length);
        String name;
        DocumentFile parent = root;
        String[] pathSegments = path.split("/");
        if (pathSegments.length > 1) {
//			String[] parentSegments = new String[pathSegments.length - 1];
//			System.arraycopy(pathSegments, 0, parentSegments, 0, pathSegments.length - 1);
//			parent = makePath(parentSegments);
            name = pathSegments[pathSegments.length - 1];
        } else {
            name = pathSegments[0];
        }
        parent = makePath(new String[]{type.getCode() + ""});
        DocumentFile file = null;
        if (parent != null) {
            file = parent.findFile(name);
            if (file != null) {
                file.delete();
            }
        }
        String mime = null;
        int dot = name.lastIndexOf(".");
        if (dot != -1) {
            mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(name.substring(dot));
        }
        if (mime == null || "null".equals(mime)) {
            mime = "application/*";
        }
        if (length == 0) {
            return null;
        }
        if (parent != null) {
            file = parent.createFile(mime, name);
        }
        OutputStream out = null;
        try {
            if (file != null) {
                out = resolver.openOutputStream(file.getUri());
            } else {
                Log.e(LOG_TAG, "Can't create file mime=" + mime + " name=" + name);
            }
            long pos = 0;
            ++recvNum;
            reporter.report(name, 0, 0, recvNum, 0);
            while (pos < length) {
                if (current.remaining() < 1) {
                    bufferPool.push(current);
                    current = in.read();
                    if (current == null) {
                        throw new EOFException("early EOF in writeFile");
                    }
                }
                int len = (int) Math.min(length - pos, current.remaining());
                if (out != null) {
                    out.write(current.array(), current.arrayOffset() + current.position(), len);
                }
                pos += len;
                current.position(current.position() + len);
                reporter.report(name, pos, length, recvNum, 0);
            }
        } catch (Throwable e) {
            close(out);
            out = null;
            Utils.deleteFile(new File(file.getUri().getPath()));
            throw e;
        } finally {
            if (out != null) {
                out.close();
            }
        }
        return file.getUri().getPath();
    }

    private void readAtLeast(int size) throws InterruptedException, EOFException {
        if (current == null) {
            current = in.read();
            if (current == null) {
                throw new EOFException("early EOF in readAtLeast");
            }
        }
        if (current.remaining() >= size) {
            return;
        }

        int sum = current.remaining();
        List<ByteBuffer> list = new ArrayList<>();
        list.add(current);
        do {
            ByteBuffer next = in.read();
            list.add(next);
            sum += next.remaining();
        } while (sum < size);

        current = ByteBuffer.allocate(sum);
        for (ByteBuffer b : list) {
            current.put(b.array(), b.arrayOffset() + b.position(), b.remaining());
            bufferPool.push(b);
        }
        current.flip();
    }

    @Override
    public void run() {
        try {
            do {
                readAtLeast(Integer.BYTES * 2 + Long.BYTES);
                current.order(ByteOrder.BIG_ENDIAN);
                int nameLen = current.getInt();
                int tmp = current.getInt();
                FileType type = FileType.valueOf(tmp);
                long fileLen = current.getLong();
                if (nameLen == 0 && fileLen == 0) {
                    Log.d(LOG_TAG, "protocol bye");
                    break; // bye
                }
                if (nameLen < 0 || nameLen > 65535 || fileLen < -1) {
                    Log.wtf(LOG_TAG, "BUG: invalid header, nameLen=" + nameLen + " fileLen=" + fileLen);
                    return;
                }
                readAtLeast(nameLen);
                String path = new String(
                        current.array(),
                        current.arrayOffset() + current.position(),
                        nameLen,
                        StandardCharsets.UTF_8);
                current.position(current.position() + nameLen);
                File f = new File(path);
                String s = f.getName();
                int i = s.lastIndexOf(".");
                if (i > 0) {
                    s = s.substring(i, s.length());
                    f = new File(f.getParentFile(), System.currentTimeMillis() + s);
                }
                s = writeFile(type, f.getAbsolutePath(), fileLen);
                if (!TextUtils.isEmpty(s)) {
                    dao.insert(new TypeFile(s, type), flag);
                }
            } while (true);
            success = true;
            reporter.report(null, 0, 0, recvNum, 0);
            Log.d(LOG_TAG, "DirectoryWriter finished normally");
        } catch (EOFException e) {
            Log.e(LOG_TAG, "DirectoryWriter early EOF", e);
        } catch (IOException e) {
            Log.e(LOG_TAG, "DirectoryWriter", e);
        } catch (InterruptedException e) {
            Log.e(LOG_TAG, "DirectoryWriter Interrupted", e);
        }
    }
}
